/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using Xceed.Silverlight.ListBox.PositioningFunction;
using System.Windows.Input;
using System.Windows.Controls;

namespace Xceed.Silverlight.ListBox
{
  public sealed class MultiViewPanel : PanelBase
  {
    #region ActiveView Attached Property

    public static readonly DependencyProperty ActiveViewProperty = DependencyProperty.RegisterAttached(
      "ActiveView",
      typeof( View ),
      typeof( MultiViewPanel ),
      new PropertyMetadata( null,
        new PropertyChangedCallback( MultiViewPanel.OnActiveViewAttachedPropertyChanged ) ) );

    public static View GetActiveView( DependencyObject obj )
    {
      return ( View )obj.GetValue( MultiViewPanel.ActiveViewProperty );
    }

    public static void SetActiveView( DependencyObject obj, View value )
    {
      obj.SetValue( MultiViewPanel.ActiveViewProperty, value );
    }

    private static void OnActiveViewAttachedPropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      ListBox listBox = sender as ListBox;

      if( listBox == null )
        return;

      MultiViewPanel panel = listBox.Panel as MultiViewPanel;

      if( panel == null )
        return;

      panel.OnActiveViewOnListBoxChanged( e.OldValue as View, e.NewValue as View );
    }

    #endregion

    #region PanelViews Attached Property

    public static readonly DependencyProperty PanelViewsProperty = DependencyProperty.RegisterAttached(
      "PanelViews",
      typeof( ReadOnlyObservableCollection<View> ),
      typeof( MultiViewPanel ),
      new PropertyMetadata( null ) );

    public static ReadOnlyObservableCollection<View> GetPanelViews( DependencyObject obj )
    {
      return ( ReadOnlyObservableCollection<View> )obj.GetValue( MultiViewPanel.PanelViewsProperty );
    }

    #endregion

    #region PositioningFunction Internal Property

    internal override PositioningFunctionBase PositioningFunction
    {
      get
      {
        View activeViewOnListBox = this.GetActiveViewOnListBox();
        return activeViewOnListBox.PositioningFunction;
      }
      set
      {
        throw new NotSupportedException();
      }
    }

    #endregion

    #region BringLastItemToStartingPoint Internal Property

    internal override bool BringLastItemToStartingPoint
    {
      get
      {
        View activeViewOnListBox = this.GetActiveViewOnListBox();

        if( activeViewOnListBox == null )
          return false;

        return activeViewOnListBox.BringLastItemToStartingPoint;
      }
    }

    #endregion

    #region Views Public Property

    public ObservableCollection<View> Views
    {
      get
      {
        if( m_views == null )
        {
          m_views = new ViewCollection();
          m_views.CollectionChanged += this.OnViewsCollectionChanged;

          // Listen to ActiveView property changed on the 
          // Views collection in case the attached property
          // is not used
          m_views.ActiveViewChanged += this.OnViewsActiveViewChanged;
        }

        return m_views;
      }
    }

    private void OnViewsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      ListBox listBox = this.ParentListBox;

      if( listBox == null )
        return;

      listBox.Refresh();
    }

    private void OnViewsActiveViewChanged( object sender, EventArgs e )
    {
      ListBox listBox = this.ParentListBox;

      // Ensure to start a ViewChanged transition when the ActiveView changes
      // on the ViewCollection to handle the case where the user sets the property
      // directly on the View
      if( listBox == null )
        return;

      MultiViewPanel.SetActiveView( this, m_views.ActiveView );
      MultiViewPanel.SetActiveView( listBox, m_views.ActiveView );
    }

    #endregion

    #region ItemOffset Internal Property

    internal override double ItemOffset
    {
      get
      {
        IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

        if( orientedViewManager != null )
        {
          return orientedViewManager.ItemsOffset( this );
        }

        return base.ItemOffset;
      }
    }

    #endregion

    #region ItemsInViewport Internal Property

    internal override double ItemsInViewport
    {
      get
      {
        IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

        if( orientedViewManager != null )
        {
          return orientedViewManager.ItemsInViewport( this );
        }

        return base.ItemsInViewport;
      }
    }

    #endregion

    #region AllowStickyHeaders Internal Read-Only Property

    internal override bool AllowStickyHeaders
    {
      get
      {
        View activeView = this.GetActiveViewOnListBox();

        if( activeView == null )
          return false;

        return activeView.AllowStickyHeaders;
      }
    }

    #endregion

    internal override bool IsContainerOutOfViewportSupported
    {
      get
      {
        // Always consider every layouted containers as into view
        // for path view.
        if( this.GetActiveViewOnListBox() is PathView )
          return false;

        return base.IsContainerOutOfViewportSupported;
      }
    }

    internal override void ForceItemOffset( double offset )
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager != null )
      {
        orientedViewManager.ForceItemOffset( this, offset );
      }
      else
      {
        base.ForceItemOffset( offset );
      }
    }

    internal override void OnParentListBoxChanged( PanelBase panel, DependencyPropertyChangedEventArgs e )
    {
      base.OnParentListBoxChanged( panel, e );

      ListBox oldParentListBox = e.OldValue as ListBox;
      ListBox newParentListBox = e.NewValue as ListBox;

      MultiViewPanel multiViewPanel = panel as MultiViewPanel;

      if( oldParentListBox != null )
        oldParentListBox.ClearValue( MultiViewPanel.ActiveViewProperty );

      multiViewPanel.ClearValue( MultiViewPanel.PanelViewsProperty );

      if( newParentListBox != null )
        newParentListBox.SetValue( MultiViewPanel.PanelViewsProperty, new ReadOnlyObservableCollection<View>( multiViewPanel.Views ) );
    }

    internal override IScalingFunction GetScalingFunction()
    {
      View activeViewOnListBox = this.GetActiveViewOnListBox();
      return activeViewOnListBox.GetScalingFunction();
    }

    internal override IOpacityFunction GetOpacityFunction()
    {
      View activeViewOnListBox = this.GetActiveViewOnListBox();
      return activeViewOnListBox.GetOpacityFunction();
    }

    internal override IZIndexFunction GetZIndexFunction()
    {
      View activeViewOnListBox = this.GetActiveViewOnListBox();
      return activeViewOnListBox.GetZIndexFunction();
    }

    internal override void EndInitCore()
    {
      IList views = this.Views;

      if( views.Count == 0 )
      {
        views.Add( new StackView() );
      }

      base.EndInitCore();
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      if( this.LayoutManager.IsLayoutInhibited )
      {
        this.ArrangeOverrideInhibited = true;
        return finalSize;
      }


      Point emptyPoint = new Point();

      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        foreach( ListBoxContainer element in this.Children )
        {
          double maxHeight = element.Configuration.CachedSize.Height;

          element.Arrange( new Rect( emptyPoint, new Size( element.DesiredSize.Width, element.DesiredSize.Height ) ) );
        }
      }
      else
      {
        orientedViewManager.ArrangeElement( this, finalSize );
      }

      RectangleGeometry clip = this.Clip as RectangleGeometry;

      if( clip == null )
        clip = new RectangleGeometry();

      clip.Rect = new Rect( emptyPoint, finalSize );
      this.Clip = clip;

      return finalSize;
    }

    internal override double GetConfigurationDistanceUsedForExtent( ListBoxContainerConfigurationBase configuration )
    {
      View activeViewOnListBox = this.GetActiveViewOnListBox();
      return activeViewOnListBox.GetConfigurationDistanceUsedForExtent( configuration );
    }

    internal override void OnScrollManagerActualScrollIndexChanged( object sender, EventArgs e )
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        base.OnScrollManagerActualScrollIndexChanged( sender, e );
      }
      else
      {
        orientedViewManager.SetOffset( this, this.ScrollManager.ActualScrollIndex );

        if( orientedViewManager.Orientation == Orientation.Vertical )
        {
          this.ScrollManager.UpdateAnimationSettings( this.ExtentHeight - this.ViewportHeight );
        }
        else
        {
          this.ScrollManager.UpdateAnimationSettings( this.ExtentWidth - this.ViewportWidth );
        }
      }
    }

    internal override double GetAverageContainerSize()
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        return base.GetAverageContainerSize();
      }
      else
      {
        return orientedViewManager.GetAverageContainerSize( this );
      }
    }

    internal void OnActiveViewOnListBoxChanged( View oldView, View newView )
    {
      ListBox listBox = this.ParentListBox;

      // If the ListBox is not yet attached to the Panel or the ListBox not yet loaded
      if( ( listBox == null ) || ( !listBox.IsLoaded ) )
      {
        if( newView != null )
          newView.PositioningFunctionChanged += this.OnActiveViewPositioningFunctionChanged;

        return;
      }

      if( newView != null )
        newView.IsActiveView = true;

      if( oldView != null )
        oldView.PositioningFunctionChanged -= this.OnActiveViewPositioningFunctionChanged;

      if( newView != null )
        newView.PositioningFunctionChanged += this.OnActiveViewPositioningFunctionChanged;

      listBox.ClearContainerConfigurationCache();

      if( oldView != null )
        this.StartViewChangedTransition();
    }

    internal void StartViewChangedTransition()
    {
      if( m_isProcessingViewChangedTransition )
        return;

      m_isProcessingViewChangedTransition = true;
      Size viewportInPixel = this.ViewportSizeInPixels;

      if( !this.LayoutManager.LaunchTransition( new ViewChangingTransitionAnimation( this.ParentListBox.AnimationSettings ), viewportInPixel, null ) )
      {
        this.LayoutManager.UpdateViewportContent( viewportInPixel );
      }

      m_isProcessingViewChangedTransition = false;
    }

    internal override void AdjustContainerTargetPosition( ListBoxContainer container )
    {
      PathView pathView = MultiViewPanel.GetActiveView( this ) as PathView;

      if( pathView != null )
      {
        // This method adjust the position of the final containers (scaled/rotated)
        // to align the container's center on the path.

        Point absoluteOffset = container.AbsoluteOffset;
        Size cachedSize = container.Configuration.CachedSize;
        double scaleFactor = container.AbsoluteScaleFactor;

        container.AbsoluteOffset = new Point( absoluteOffset.X - ( ( cachedSize.Width / 2 ) * scaleFactor ),
             absoluteOffset.Y - ( ( cachedSize.Height / 2 ) * scaleFactor ) );
      }
      base.AdjustContainerTargetPosition( container );
    }

    internal override void SetHorizontalOffsetCore( double offset )
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        base.SetHorizontalOffsetCore( offset );
      }
      else
      {
        orientedViewManager.SetHorizontalOffset( this, offset );
      }
    }

    internal override void SetVerticalOffsetCore( double offset )
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        base.SetVerticalOffsetCore( offset );
      }
      else
      {
        orientedViewManager.SetVerticalOffset( this, offset );
      }
    }

    internal override void ProcessMouseWheel( MouseWheelEventArgs e )
    {
      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager == null )
      {
        base.ProcessMouseWheel( e );
      }
      else
      {
        orientedViewManager.ProcessMouseWheel( this, e );
      }
    }

    internal override ContainerLayoutMode GetPositioningContainerLayoutMode()
    {
      View activeViewOnListBox = this.GetActiveViewOnListBox();
      return activeViewOnListBox.PositioningContainerLayoutMode;
    }

    internal override Orientation GetScrollingOrientation()
    {
      IOrientedView activeViewOnListBox = this.GetActiveViewOnListBox() as IOrientedView;

      if( activeViewOnListBox != null )
        return activeViewOnListBox.Orientation;

      return base.GetScrollingOrientation();
    }

    internal override AddingItemTransitionAnimation CreateAddingItemTransitionAnimation()
    {
      AddingItemTransitionAnimation animation = base.CreateAddingItemTransitionAnimation();

      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager != null )
      {
        // Here we use the ScrollOrientation Property of the OrientedView because in some cases 
        // the scroll orientation behavior dose not follow the view orientation. 
        // For example: The vertical WrapPanel scroll items horizontally.
        animation.Orientation = orientedViewManager.ScrollOrientation;
        return animation;
      }

      return animation;
    }

    internal override RemovingItemTransitionAnimation CreateRemovingItemTransitionAnimation()
    {
      RemovingItemTransitionAnimation animation = base.CreateRemovingItemTransitionAnimation();

      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager != null )
      {
        // Here we use the ScrollOrientation Property of the OrientedView because in some cases 
        // the scroll orientation behavior dose not follow the view orientation. 
        // For example: The vertical WrapPanel scroll items horizontally.
        animation.Orientation = orientedViewManager.ScrollOrientation;
      }

      return animation;
    }

    internal override TransitionAnimation CreateScrollingUpTransitionAnimation()
    {
      TransitionAnimation animation = null;

      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager != null )
      {
        // Here we use the ScrollOrientation Property of the OrientedView because in some cases 
        // the scroll orientation behavior dose not follow the view orientation. 
        // For example: The vertical WrapPanel scroll items horizontally.
        ScrollingUpTransitionAnimation scrollAnimation = new ScrollingUpTransitionAnimation( this.ParentListBox.AnimationSettings );
        scrollAnimation.Orientation = orientedViewManager.ScrollOrientation;
        animation = scrollAnimation;
      }
      else
      {
        // No orientation
        // We can't have a scrolling directon for now so
        // we use a LoadingAnimation
        animation = new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
      }

      return animation;
    }

    internal override TransitionAnimation CreateScrollingDownTransitionAnimation()
    {
      TransitionAnimation animation = null;

      IOrientedViewManager orientedViewManager = this.GetActiveViewOnListBox() as IOrientedViewManager;

      if( orientedViewManager != null )
      {
        // Here we use the ScrollOrientation Property of the OrientedView because in some cases 
        // the scroll orientation behavior dose not follow the view orientation. 
        // For example: The vertical WrapPanel scroll items horizontally.
        ScrollingDownTransitionAnimation scrollAnimation = new ScrollingDownTransitionAnimation( this.ParentListBox.AnimationSettings );
        scrollAnimation.Orientation = orientedViewManager.ScrollOrientation;
        animation = scrollAnimation;
      }
      else
      {
        // No orientation
        // We can't have a scrolling directon for now so
        // we use a LoadingAnimation
        animation = new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
      }

      return animation;
    }


    #region Private Methods

    internal View GetActiveViewOnListBox()
    {
      ListBox listBox = this.ParentListBox;

      if( listBox == null )
        throw new ListBoxInternalException( "No ListBox associated with a MultiViewPanel." );

      View activeViewOnListBox = MultiViewPanel.GetActiveView( listBox );

      if( activeViewOnListBox == null )
      {
        // Affect the MultiViewPanel.ActiveView Attached Property on the ListBox, not
        // this.ListBoxActiveView directly to be notified of changes when someone affects
        // it on the ListBox in xaml or code behind.
        View activeView = ( ( ViewCollection )this.Views ).ActiveView;

        MultiViewPanel.SetActiveView( listBox, activeView );
        MultiViewPanel.SetActiveView( this, activeView );
        activeViewOnListBox = activeView;
      }

      Debug.Assert( activeViewOnListBox != null );
      if( activeViewOnListBox == null )
        throw new ListBoxInternalException( "No active View was found on the ListBox." );

      return activeViewOnListBox;
    }

    private void OnActiveViewPositioningFunctionChanged( object sender, EventArgs e )
    {
      if( this.IsInitialized )
        this.LayoutManager.LaunchViewChangedTransition();
    }

    #endregion

    #region Private Fields

    private ViewCollection m_views;
    private bool m_isProcessingViewChangedTransition = false;

    #endregion
  }
}
